<?php

require_once 'IFXField.class.php';

/**
 * 	IntuiFormX Class
 *
 * 	@package IntuiFormX
 *	@author Jeremy Clifton <j.clifton@4-8-4.com>
 *	@copyright 2004-2009
 *	@version 7.0.7 (2009-10-16)
 */
class IntuiFormX
{
	
	/**
	 *	Additional error messages that can be accessed somewhere ... somehow ...
	 *
	 *	@access private
	 *	@var array
	 */
	private $error_messages = array();
	
	/**
	 *	Contains the names of fields that did not validate.
	 *
	 *	@access private
	 *	@var array
	 */
	private $errors = array();
	
	/**
	 *	Associative array that holds all the defined IntuiFormField objects.
	 *
	 *	@access private
	 *	@var array
	 */
	private $field_objects = array();
	
	/**
	 *	Associative array with bound form input.
	 *
	 *	@access private
	 *	@var array
	 */
	private $input = array();
	
	/**
	 *	Holds the different form field templates for this form.
	 *
	 *	@access private
	 *	@var array
	 */
	private $templates = array();
	
	/**
	 *	Constructor
	 */
	public function __construct($input = array()) { $this->bind($input); }
	
	/**
	 *	Adds a new form field to the form object.
	 *
	 *	@access public
	 *	@param array $params
	 *	@return object [IFXField]
	 */
	public function add($params = array())
	{
	
		// make sure we have a name
		if (empty($params['name'])) {
			throw new Exception('No field name was specified!');
			return;
		}
		
		// if no id was specified, we just use the field name
		if (empty($params['id'])) $params['id'] = $params['name'];
		
		// try to bind to either an associative array or an object
		if (is_array($this->input)) {
			if (isset($this->input[$params['name']]) and !is_null($this->input[$params['name']])) {
				$params['currentVal'] = $this->input[$params['name']];
			}
		} else if (is_object($this->input)) {
			$t = $this->input->getTable();
			if ($t->hasColumn($params['name'])) {
				if (!is_null($this->input->$params['name'])) {
					$params['currentVal'] = $this->input->$params['name'];
				}
			}
		}
		
		$object = new IFXField($params);
		$this->field_objects[$params['id']] = $object;
		return $object;
		
	}
	
	/**
	 *	Builds a group of checkboxes.
	 *
	 *	@access public
	 *	@param string $name
	 *	@param array $fields
	 *	@param array $params
	 *	@return array
	 */
	public function add_checkbox_group($name, $fields = array(), $params = array())
	{
		if (!is_array($params['currentVal'])) $value = array($params['currentVal']);
		$group = array();
		foreach ($fields as $fld) {
			if (in_array($fld['checkedVal'], $params['currentVal'])) {
				$fld_value = $fld['checkedVal'];
			}
			$fieldClass = (!empty($fld['class']))?$fld['class']:$params['class'];
			$labelClass = (!empty($fld['labelClass']))?$fld['labelClass']:$params['labelClass'];
			$object = new IFXField(array(
				'fieldobject' => $this,
				'id' => $fld['id'],
				'type' => 'checkbox',
				'name' => $name.'[]',
				'checkedVal' => $fld['checkedVal'],
				'currentVal' => $fld_value,
				'defaultVal' => $params['defaultVal'],
				'disabled' => $fld['disabled'],
				'class' => $fieldClass,
				'label' => $fld['label'],
				'labelClass' => $labelClass
			));
			$this->field_objects[$fld['id']] = $object;
			$group[] = $object;
		}
		return $group;
	}
	
	/**
	 *	Adds an error message to the list.
	 *
	 *	@access public
	 *	@param string $message
	 */
	public function add_error_message($message) { $this->error_messages[] = $message; }
	
	/**
	 *	Builds a group of radio buttons.
	 *
	 *	@access public
	 *	@param string $name
	 *	@param array $fields
	 *	@param array $params
	 *	@return array
	 */
	public function add_radio_group($name, $fields = array(), $params = array())
	{
		$group = array();
		foreach ($fields as $fld) {
			$fieldClass = (!empty($fld['class']))?$fld['class']:$params['class'];
			$labelClass = (!empty($fld['labelClass']))?$fld['labelClass']:$params['labelClass'];
			$object = new IFXField(array(
				'formobject' => $this,
				'id' => $fld['id'],
				'type' => 'radio',
				'name' => $name,
				'checkedVal' => $fld['checkedVal'],
				'currentVal' => $params['currentVal'],
				'defaultVal' => $params['defaultVal'],
				'disabled' => $fld['disabled'],
				'class' => $fieldClass,
				'label' => $fld['label'],
				'labelClass' => $labelClass
			));
			$this->field_objects[$fld['id']] = $object;
			$group[] = $object;
		}
		return $group;
	}
	
	/**
	 *	Sets the value of any form field whose name matches a key in the specified associative array to the value
	 * 	contained in the associative array.
	 *
	 *	@access public
	 *	@param array $input
	 */
	public function bind($input = array()) { $this->input = $input; }
	
	/**
	 *	Defines templates for this form. If a template with a specified key exists, it will be replaced with the
	 *	new template.
	 *
	 *	@access public
	 *	@param array $templates
	 */
	public function define_templates($templates)
	{
		foreach ($templates as $k => $v) $this->templates[$k] = $v;
	}
	
	/**
	 *	Retrieves the list of error messages.
	 *
	 *	@access public
	 *	@return array
	 */
	public function error_messages() { return $this->error_messages; }
	
	/**
	 *	Returns the error messages as an unordered list.
	 *
	 *	@access public
	 *	@return string
	 */
	public function error_messages_as_list()
	{
		if (sizeof($this->error_messages) == 0) return null;
		$list = '<ul class="ifx-error-messages">';
		foreach ($this->error_messages as $err) $list .= "<li>{$err}</li>";
		$list .= '</ul>';
		return $list;
	}
	
	/**
	 *	Retrieves the list of field names that did not validate.
	 *
	 *	@access public
	 *	@return array
	 */
	public function errors() { return $this->errors; }
	
	/**
	 *	Renders the specified field.
	 *
	 *	@access public
	 *	@param string $domid
	 *	@return object
	 */
	public function field($domid)
	{ 
		$obj = $this->field_objects[$domid];
		if (!is_object($obj)) {
			trigger_error('Could not find a field object named "'.$domid.'"!', E_USER_WARNING);
			return;
		}
		return $obj->render() ;
	}
	
	/**
	 *	Returns the rendered field followed by the rendered label.
	 *
	 *	@access public
	 *	@param string $domid
	 *	@param string $separator
	 *	@return string
	 */
	public function field_and_label($domid, $separator = ' ') { return $this->field($domid).$separator.$this->label($domid); }
	
	/**
	 *	Returns an object reference for the specified field object.
	 *
	 *	@access public
	 *	@param string $domid
	 *	@return object
	 */
	public function get_field($domid) { return $this->field_objects[$domid]; }
	
	/**
	 *	Returns all of the field objects.
	 *
	 *	@access public
	 *	@return array
	 */
	public function get_fields() { return $this->field_objects; }
	
	/**
	 *	Checks to see if a field is indicated as required.
	 *
	 *	@access public
	 *	@param $field
	 *	@return boolean
	 */
	public function is_required($field)
	{
		$obj = $this->get_field($field);
		return $obj->isDefault();
	}
	
	/**
	 *	Renders the specified field's label.
	 *
	 *	@access public
	 *	@param string $domid
	 *	@return object
	 */
	public function label($domid)
	{
		$obj = $this->field_objects[$domid];
		if (!is_object($obj)) {
			trigger_error('Could not find a field object named "'.$domid.'"!', E_USER_WARNING);
			return;
		}
		return $obj->getLabel();
	}
	
	/**
	 *	Returns the rendered label followed by the rendered field.
	 *
	 *	@access public
	 *	@param string $domid
	 *	@param string $separator
	 *	@return string
	 */
	public function label_and_field($domid, $separator = ' ') { return $this->label($domid).$separator.$this->field($domid); }
	
	/**
	 *	Sets the specified field as invalid.
	 *
	 *	@access public
	 *	@param string $key
	 */
	public function set_invalid($key)
	{
		$field = $this->field_objects[$key];
		if (is_object($field)) {
			$this->errors[] = $key;
			$field->setInvalid();
		}
	}

	
	/**
	 *	Renders a field and label according to the specified template. Will look for and replace the following tags
	 *	with appropriate information from the specified field object: %field%, %field_class%, %field_type%,
	 *	%label%, and %label_class%. If $template_name is not specified and no template was explicitly assigned to
	 *	this field, will default to 'standard'. Returns null if no templates are defined.
	 *
	 *	@access public
	 *	@param string $field_name
	 *	@param string $template_name
	 *	@return string or null
	 */
	public function template($field_name, $template_name = null)
	{
	
		// if there are no templates, return null	
		if (sizeof($this->templates) == 0) return null;
		
		// grab the field object
		$the_field = $this->field_objects[$field_name];
		
		// if we weren't passed a template name and one was explicitly assigned to the field, we'll use that;
		// otherwise we default to 'standard' (unless there is no 'standard' ... then we return null)
		if (is_null($template_name) and !is_null($the_field->getTemplate())) $template_name = $the_field->getTemplate();
		if (!array_key_exists('standard', $this->templates)) return null;
		if (is_null($template_name)) $template_name = 'standard';
		
		// do tag replacement and return the output
		$output = str_replace('%label%', $this->label($field_name), $this->templates[$template_name]);
		$output = str_replace('%field%', $this->field($field_name), $output);
		$output = str_replace('%label_class%', $the_field->getLabelClass(), $output);
		$output = str_replace('%field_class%', $the_field->getClass(), $output);
		$output = str_replace('%field_type%', $the_field->getType(), $output);
		return $output;
		
	}
	
	/**
	 *	Tries to validate bound input against the regexes defined for each field. Optionally calls $callback to do
	 *	additional validation.
	 *
	 *	@access public
	 *	@param mixed $callback
	 *	@return bool
	 */
	public function validate($callback = null)
	{
		
		// automated checks based on fields being required and/or their validator regex pattern
		$errors = array();
		foreach ($this->field_objects as $fo) {
			$input = $this->input[$fo->getName()];
			if (!is_array($input)) $input = array($input);
			foreach ($input as $i) {
				if (!$fo->validate($i)) {
					$fo->setInvalid();
					$errors[] = $fo->getName();
				}
			}
		}
		
		// run any additional error checking via a callback
		if (!empty($callback)) call_user_func_array($callback, array(&$this, &$errors));
		
		// make sure the values in $this->errors are unique
		$this->errors = array_unique($errors);
		
		// let the developer know if any errors were found
		return (sizeof($errors) == 0) ? true : false ;
		
	}

}

?>